using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.IO;
using System.ComponentModel;

namespace Stone.Components
{
    [Serializable()]
    public abstract class XmlSerializerBase : IXmlSerializable2
    {
        public delegate void ReadXmlElementHandler(string name, XmlReader reader);

        private object syncRoot = new object();

        public XmlSerializerBase(string elementName)
        {
            this.xmlElementName = elementName;
        }

        protected virtual void BeforeReadXml() { }
        protected virtual void AfterReadXml() { }
        protected virtual void BeforeWriteXml() { }
        protected virtual void AfterWriteXml() { }
        protected virtual void BeforeInitXmlWriter(XmlWriterSettings settings) { }

        private string xmlElementName;
        public virtual string XmlElementName
        {
            get { return xmlElementName; }
        }

        public virtual XmlSchema GetSchema()
        {
            return null;
        }

        public virtual void ReadXml(XmlReader reader)
        {
            ReadXml(reader, XmlElementName);
        }

        public virtual bool ReadXml(XmlReader reader, string elementName)
        {
            reader.MoveToElement();
            if (String.Equals(elementName, reader.Name, StringComparison.OrdinalIgnoreCase))
            {
                BeforeReadXml();

                ReadXmlAttributes(reader);

                reader.MoveToElement();
                if (!reader.IsEmptyElement)
                {
                    if (reader.NodeType == XmlNodeType.Element || reader.MoveToElement())
                    {
                        ReadXmlChildElements(reader);
                    }
                }

                AfterReadXml();

                return true;
            }
            else
            {
                return false;
            }
        }

        internal void ReadXmlAttributes(XmlReader reader)
        {
            for (int i = 0; i < reader.AttributeCount; i++)
            {
                reader.MoveToAttribute(i);
                ReadXmlAttribute(reader.Name, reader.Value, reader);
            }
        }

        internal void ReadXmlChildElements(XmlReader reader, ReadXmlElementHandler handler)
        {
            reader.MoveToElement();
            reader = reader.ReadSubtree();
            if (reader.Read())
            {
                while (reader.Read() && reader.NodeType != XmlNodeType.Element) ;
                XmlReader childReader = null;
                while (reader.NodeType == XmlNodeType.Element)
                {
                    childReader = reader.ReadSubtree();
                    if (childReader.Read() && handler != null)
                    {
                        handler(
                            reader.Name == null ? String.Empty : reader.Name, 
                            childReader);
                    }
                    childReader.Close();

                    while (reader.Read() && reader.NodeType != XmlNodeType.Element) ;
                }
            }
        }

        internal void ReadXmlChildElements(XmlReader reader)
        {
            ReadXmlChildElements(reader, ReadXmlChildElement);
        }

        internal abstract void ReadXmlAttribute(string name, string value, XmlReader reader);


        internal abstract void ReadXmlChildElement(string name, XmlReader reader);

        public void WriteXml(XmlWriter writer)
        {
            WriteXml(writer, XmlElementName);
        }

        public void WriteXml(XmlWriter writer, bool writeStartElement)
        {
            WriteXml(writer, writeStartElement ? XmlElementName : null);
        }

        public void WriteXml(XmlWriter writer, string elementName)
        {
            BeforeWriteXml();

            bool writeElementName = !String.IsNullOrEmpty(elementName);
            if (writeElementName) writer.WriteStartElement(elementName);
            WriteXmlAttributes(writer);
            WriteXmlChildElements(writer);
            if (writeElementName) writer.WriteEndElement();

            AfterWriteXml();
        }


        internal abstract void WriteXmlAttributes(XmlWriter writer);

        internal abstract void WriteXmlChildElements(XmlWriter writer);

        protected virtual XmlWriterSettings GetDefaultXmlWriterSettings()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new System.Text.UTF8Encoding(false);

            return settings;
        }

        public virtual string ToXml()
        {
            return ToXml(XmlElementName);
        }

        public string ToXml(string elementName)
        {
            return ToXml(true, true, XmlElementName);
        }

        public virtual string ToXml(bool hasDeclareHead, bool formated)
        {
            return ToXml(hasDeclareHead, formated, XmlElementName);
        }

        public virtual string ToXml(bool hasDeclareHead, bool formated, string elementName)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = !hasDeclareHead;
            settings.Indent = formated;
            settings.Encoding = new System.Text.UTF8Encoding(false);
            return ToXml(settings, elementName);
        }

        public virtual string ToXml(XmlWriterSettings settings, string elementName)
        {
            if (settings == null)
            {
                settings = new XmlWriterSettings();
                settings.Encoding = new System.Text.UTF8Encoding(false);
            }

            BeforeInitXmlWriter(settings);

            using (MemoryStream stream = new MemoryStream(1024 * 8))
            {
                XmlWriter xmlWriter = XmlWriter.Create(stream, settings);

                WriteXml(xmlWriter, elementName);

                xmlWriter.Flush();
                return settings.Encoding.GetString(stream.ToArray());
            }
        }

        public virtual bool FromXml(string xmlString)
        {
            return FromXml(xmlString, XmlElementName);
        }

        public virtual bool FromXml(string xmlString, string elementName)
        {
            XmlReader xmlReader = XmlReader.Create(new StringReader(xmlString));

            while (xmlReader.Read() && xmlReader.NodeType != XmlNodeType.Element) ;
            if (xmlReader.NodeType == XmlNodeType.Element)
            {
                return ReadXml(xmlReader, elementName);
            }

            return false;
        }
    }
}
